package com.thoughtworks.bytecode2class;

import com.thoughtworks.bytecode2class.attribute.*;
import com.thoughtworks.bytecode2class.instruction.Instruction;

import java.io.PrintStream;

public class DefaultClassVisitor implements ClassVisitor {

    private PrintStream out;

    public static final String SPERATOR = "\t";
    private static final String s = SPERATOR;

    public DefaultClassVisitor() {
        this(System.out);
    }

    DefaultClassVisitor(PrintStream out) {
        this.out = out;
    }

    public void beginConstants() {
        out.printf("Constant Pool:\n");
    }

    public void beginFields() {
        out.printf("Field:\n");
    }

    public void acceptMedhodAndFieldRef(MethodAndFieldRef field) {
        out.printf(s);
        for (AccessFlag accessFlag : field.getAccessFlags()) {
            out.printf("%s ", accessFlag);
        }
        out.printf("%s [%s]\n", field.getName(), field.getDescriptor());
    }

    public void beginMethods() {
        out.printf("Method:\n");
    }

    public void visitMethod(MethodAndFieldRef method) {
        out.printf(s + "%s [%s] %s\n", method.getName(), method.getDescriptor(), getExceptionsInfo(method));
        CodeAttribute codeAttribute = method.getCodeAttribute();
        if (codeAttribute == null) return;

        out.printf(s + s + "Code:\n");
        out.printf(s + s + s + "MaxStack: %s\n", codeAttribute.getMaxStack());
        out.printf(s + s + s + "MaxLocals: %s\n", codeAttribute.getMaxLocals());
        out.printf(s + s + s + "Method body:\n");

        for (Instruction instruction : codeAttribute.getCode()) {
            visitInstruction(instruction);
        }

        out.printf(s + s + s + "Exceptions:\n");
        out.printf(s + s + s + "From" + s + "To" + s + s + "Target" + s + "Type\n");

        for (ExceptionInfo e : codeAttribute.getExceptionTable()) {
            out.printf(s + s + s + "%s" + s + s + "%s" + s + s + "%s" + s + s + "%s\n",
                    e.getStartPc(), e.getEndPc(), e.getHandlerPc(), e.getCatchType());
        }
    }

    public void visitInstruction(Instruction instruction) {
        out.printf(s + s + s + s + "%s %s %s\n", instruction.getOffset(), instruction.getMemoni(), instruction.getParameter());
    }

    public void beginAttributes() {
        out.println("Attributes:");
    }

    public void visitAttribute(Attribute attribute) {
        out.printf(s + "%s\n", attribute.getName());
        if (attribute instanceof InnerClassesAttribute) {
            printInnerClassesAttribute((InnerClassesAttribute) attribute);
        } else if (attribute instanceof SourceFileAttribute) {
            printSourceFileAttribute((SourceFileAttribute) attribute);
        }
    }

    private String getExceptionsInfo(MethodAndFieldRef method) {
        ExceptionsAttribute exceptionsAttribute = method.getExceptionsAttribute();
        if (exceptionsAttribute == null) {
            return "";
        }
        StringBuilder builder = new StringBuilder("throws");
        for (String exception : exceptionsAttribute.getExceptions()) {
            builder.append(" " + exception);
        }
        return builder.toString();
    }

    private void printSourceFileAttribute(SourceFileAttribute attribute) {
        out.printf(s + s + "%s\n", attribute.getSourceFile());
    }

    private void printInnerClassesAttribute(InnerClassesAttribute attribute) {
        for (InnerClassesAttribute.InnerClass innerClass : attribute.getInnerClasses()) {
            out.printf(s + s + "Name: %s\n", innerClass.getName());
            out.printf(s + s + "Inner Class: %s\n", innerClass.getInnerClass());
            out.printf(s + s + "Outer Class: %s\n", innerClass.getOuterClass());
        }
    }
}
